#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <typeinfo>

#include "stf.hpp"

using namespace std;
using stfu::node;
using stfu::child;
using stfu::value;

ostream &stfu::operator<< (ostream &out, const node &root) {
    root.write(out);
    return out;
}

bool stfu:: operator<< (const char *filename, const node &root){
    return root.write(filename);
}

bool stfu::operator<< (ofstream &out, const node &root) {
    return root.write(out);
}

bool stfu::operator>> (istream &in, node &root) {
    return root.read(in);
}

bool stfu::operator>> (const char *filename, node &root) {
    return root.read(filename);
}

const string &node::getValue(const string &name, size_t index) const throw (out_of_range) {
    return get_indexed<string, string>(values, name, index);
}

/*Function is const, but shouldn't be called on const objects since it returns a nonconst-reference to a member*/
string &node::getValue(const string &name, size_t index) throw (out_of_range) {
    return get_indexed<string, string>(values, name, index);
}

string &node::addValue(const string &name) {
    return values.insert(pair<string, string>(name, string()))->second;
}

string &node::value(const string &name, size_t index) {
    try {
        return getValue(name, index);
    } catch (out_of_range &e) {
        //it doesn't exist: create it
        return addValue(name);
    }
}

void node::removeValue(const string &name, size_t index) throw (out_of_range) {
    values.erase(get_indexed_it(values, name, index));
    return;
}

void node::renameValue(const string &oldName, const string &newName, size_t index) {
    addValue(newName) = value(oldName, index);
    removeValue(oldName, index);
}


const node &node::getChild(const string &name, size_t index) const throw (out_of_range) {
    return get_indexed<string, node>(children, name, index);
}

node &node::getChild(const string &name, size_t index) throw (out_of_range) {
    return get_indexed<string, node>(children, name, index);
}

node &node::addChild(const string &name) {
    return children.insert(pair<string, node>(name, node()))->second;
}

node &node::addChild(const string &name, node &newNode) {
    return children.insert(pair<string, node>(name, newNode))->second;
}

node &node::child(const string &name, size_t index) {
    //if there's no such child, add one
    try {
        return getChild(name, index);
    } catch (out_of_range &e) {
        //it doesn't exist: create it
        return addChild(name);
    }
}

void node::renameChild(const string &oldName, const string &newName, size_t index) {
    node copy = child(oldName, index);
    removeChild(oldName, index);
    addChild(newName) = copy;
}

void node::removeChild(const string &name, size_t index) throw (out_of_range) {
    children.erase(get_indexed_it(children, name, index));
    return;
}

bool node::read(const char *filename) {
    ifstream f(filename);

    if (!f.good()) return false;

    bool success = read(f);
    f.close();

    return success;
}

bool node::read(istream &in) {
    while (1) {
        in >> ws; //Skip whitespace

        //if end of node is reached, return
        if (in.peek() == '}') {
            in.ignore();
            return true;
        }

        if (in.eof()) {
            return true;            //End of the file is reached
        }

        string name;
        char type; // '{' or '"'
        streamRead(in, name, ':');          //Read name (all chars before ':')
        type = streamSkip(in,"\"{");      //Skip everything until '{' or '"'

        switch (type) {

        //in case of value
        case '"': {
            string value;
            while (1) {
                if (streamRead(in, value, '"') == 0) { //Read to the closing-"
                    return false;
                }
                if (in.peek() == '"') {
                    in.ignore();
                    value += '"';
                    continue;
                } else {
                    break;
                }
            }
            this->values.insert(pair<string,string>(name, value));
            break;
        }

        //in case of child
        case '{': {
            node sub;
            if (!sub.read(in)) {         //Recursively read the subnode
                return false;
            }
            this->children.insert(pair<string,node>(name,sub));
            break;
        }

        default:
            return false;
        }
    }
}

/*Writes to a file using it's overloaded self*/
bool node::write(const char *filename) const {
    ofstream f(filename);

    if (!f.good()) {
        return false;
    }

    bool success = write(f);
    f.close();

    return success;
}

bool node::write(ostream &out, size_t depth, string indent) const {
    string indentation;
    for (size_t i = 0; i < depth; i++) {
        indentation += indent;
    }

    for (multimap<string, string>::const_iterator value_it = values.begin(); value_it != values.end(); value_it++) {
        //Escape all the '"' by adding a second '"'
        string value(value_it->second);
        size_t found = value.find('"');

        //while there are more ", insert second "s
        while (found != value.npos) {
            value.insert(found, 1, '"');
            found = value.find('"', found+2);
        }
        out << indentation << value_it->first << ": \"" << value << '"' << endl;
    }

    for (multimap<string, node>::const_iterator child_it = children.begin(); child_it != children.end(); child_it++) {
        out << indentation << child_it->first << ": {" << endl;
        child_it->second.write(out, depth+1);
        out << indentation << '}' << endl;
    }

    return true;
}

char node::streamSkip(istream &in, const string &delimiters) {
    char cur;

    //Return if the current char is part of delimiters[]
    while (in >> noskipws >> cur) {
        if (delimiters.find_first_of(cur) != delimiters.npos) return cur;
    }
    return 0;
}

char node::streamRead(istream &in, string &out, const string &delimiters) {
    char cur;

    //Return if the current char is part of delimiters[]
    while (in >> noskipws >> cur) {
        if (delimiters.find(cur) != delimiters.npos) return cur;
        out += cur;
    }
    return 0;
}

char node::streamRead(istream &in, string &out, const char delimiter) {
    char cur;

    //Return if the current char is delimiter
    while (in >> noskipws >> cur) {
        if (delimiter == cur) return cur;
        out += cur;
    }
    return 0;
}
